package tmt.usercenter.web.service;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tmt.usercenter.web.domain.ClientSystem;
import tmt.usercenter.web.domain.TmtApproval;
import tmt.usercenter.web.domain.TmtUser;
import tmt.usercenter.web.repository.ITmtApprovalRepo;

import javax.persistence.criteria.Predicate;
import java.util.*;

/**
 * @author Stark
 * @date 2017/11/11
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class TmtApprovalService extends TmtBaseService<TmtApproval, Long> implements ApprovalStore {

	public static final String CACHE_APPROVAL = "Approval";

	@Getter
	private ITmtApprovalRepo approvalRepo;
	private TmtUserService userService;
	private ClientSystemService clientSystemService;

	@Autowired
	public TmtApprovalService(ITmtApprovalRepo iTmtApprovalRepo,
							ClientSystemService clientSystemService, TmtUserService userService){
		super(iTmtApprovalRepo, Arrays.asList(clientSystemService, userService));
		this.approvalRepo = iTmtApprovalRepo;
		this.clientSystemService = clientSystemService;
		this.userService = userService;
	}

	@CacheEvict(cacheNames = CACHE_APPROVAL, key = "'TmtApproval:UserId:' + #entity.tmtUser.id")
	public TmtApproval create(TmtApproval entity) throws Exception {
		return super.create(entity);
	}

	@CacheEvict(cacheNames = CACHE_APPROVAL, key = "'TmtApproval:UserId:' + #userId")
	public List<TmtApproval> createAll(Long userId, List<TmtApproval> list) throws Exception {
		return super.createAll(list);
	}

	@Transactional(readOnly = true)
	@Cacheable(cacheNames = CACHE_APPROVAL, key = "'TmtApproval:UserId:' + #id", unless = "#result == null")
	public List<TmtApproval> findByTmtUserId(Long id, String[] lazyProperties) {
		List<TmtApproval> list = approvalRepo.findByTmtUserId(id);
		loadLazyProperties(list, lazyProperties);
		return list;
	}

	@Transactional(readOnly = true)
	public boolean existsByUserIdAndClientIdAndScope(String userId, String clientId, String scope) {
		TmtUser user = new TmtUser();
		user.setUsername(userId);
		ClientSystem clientSystem = new ClientSystem();
		clientSystem.setClientId(clientId);

		TmtApproval tmtApproval = new TmtApproval();
		tmtApproval.setTmtUser(user);
		tmtApproval.setOriginSystem(clientSystem);
		tmtApproval.setScope(scope);

		//创建匹配器，即如何使用查询条件
		ExampleMatcher matcher = ExampleMatcher.matching()
				.withMatcher("tmtUser.username", ExampleMatcher.GenericPropertyMatchers.exact())
				.withMatcher("originSystem.clientId", ExampleMatcher.GenericPropertyMatchers.exact())
				.withMatcher("scope", ExampleMatcher.GenericPropertyMatchers.exact());

		//创建实例
		Example<TmtApproval> example = Example.of(tmtApproval, matcher);
		return approvalRepo.exists(example);
	}

	@Transactional(readOnly = true)
	public List<TmtApproval> findByUserIdAndClientIdAndScope(String userId, String clientId, String scope) {
		Specification<TmtApproval> s = (root, query, cb) -> {
			Predicate p1 = cb.equal(root.get("tmtUser").get("username"), userId);
			Predicate p2 = cb.equal(root.get("originSystem").get("clientId"), clientId);
			Predicate p3 = cb.equal(root.get("scope"), scope);
			Predicate predicate = cb.and(p1, p2, p3);
			return query.where(predicate).getRestriction();
		};
		return approvalRepo.findAll(s);
	}

	@Override
	public boolean addApprovals(Collection<Approval> approvals) {
		try {
			Vector<TmtApproval> tmtApprovals = new Vector<>();
			approvals.forEach(x -> {
				if (!existsByUserIdAndClientIdAndScope(x.getUserId(), x.getClientId(), x.getScope())) {
					tmtApprovals.add(toTmtApproval(x));
				}
			});
			long userId = tmtApprovals.get(0).getTmtUser().getId();
			createAll(userId, tmtApprovals);
			return true;
		}catch (Exception ex){}
		return false;
	}

	@CacheEvict(cacheNames = CACHE_APPROVAL, key = "'TmtApproval:UserId:' + #entity.tmtUser.id")
	@Override
	public boolean revokeApprovals(Collection<Approval> approvals) {
		approvals.forEach(x -> {
			List<TmtApproval> tmtApprovals = findByUserIdAndClientIdAndScope(x.getUserId(), x.getClientId(), x.getScope());
			if (tmtApprovals != null && tmtApprovals.size() > 0) {
				approvalRepo.delete(tmtApprovals);
			}
		});
		return true;
	}

	@Transactional(readOnly = true)
	@Override
	public List<Approval> getApprovals(String userId, String clientId) {
		List<TmtApproval> tmtApprovals = approvalRepo.findByUsernameAndClientId(userId, clientId);
		List<Approval> approvals = new ArrayList<>();
		tmtApprovals.forEach(x -> approvals.add(toApproval(x)));
		return approvals;
	}

	private Approval toApproval(TmtApproval tmtApproval) {
		return new Approval(
				tmtApproval.getTmtUser().getUsername(),
				tmtApproval.getOriginSystem().getClientId(),
				tmtApproval.getScope(),
				tmtApproval.getExpiresAt(),
				tmtApproval.getStatus(),
				tmtApproval.getLastUpdatedAt()
		);
	}

	private TmtApproval toTmtApproval(Approval approval) {
		TmtUser user = new TmtUser();
		Long userId = userService.findIdByUsername(approval.getUserId());
		user.setId(userId);

		ClientSystem clientSystem = new ClientSystem();
		Long systemId = clientSystemService.findIdByClientId(approval.getClientId());
		clientSystem.setId(systemId);

		TmtApproval tmtApproval = new TmtApproval();
		tmtApproval.setTmtUser(user);
		tmtApproval.setOriginSystem(clientSystem);
		tmtApproval.setScope(approval.getScope());
		tmtApproval.setExpiresAt(approval.getExpiresAt());
		tmtApproval.setLastUpdatedAt(approval.getLastUpdatedAt());
		tmtApproval.setStatus(approval.getStatus());
		return tmtApproval;
	}
}
